package com.jake.summer.framework.util;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ClassUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 类操作工具类
 */
@Slf4j
public final class SummerClassUtils {

    /**
     * @return 获取当前线程的类加载器
     */
    public static ClassLoader getClassLoader() {
        return Thread.currentThread().getContextClassLoader();
    }

    /**
     * 根据包名，直接获取该包路径下的类对象，或获取该包路径下的jar包中包含的所有类对象，存入集合中并返回。
     * @param packageName 包名
     * @return 返回指定包名下的所有类
     */
    public static Set<Class<?>> getClassSet(String packageName) {
        Set<Class<?>> classSet = new HashSet<>();
        try {
            Enumeration<URL> urls = getClassLoader().getResources(StringUtils.replace(packageName, ".", "/"));
            while (urls.hasMoreElements()) {
                URL url = urls.nextElement();
                if (url != null) {
                    String protocol = url.getProtocol();
                    if (StringUtils.equalsIgnoreCase(protocol, "file")) {
                        String packagePath = StringUtils.replace(url.getPath(), "%20", " ");
                        addClass(classSet, packagePath, packageName);
                    } else if (StringUtils.equals(protocol, "jar")) {
                        // 如果 url 资源为 jar 包，则遍历获取 jar 包文件中的所有 class 文件，并存入集合中。
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        if (jarURLConnection != null) {
                            JarFile jarFile = jarURLConnection.getJarFile();
                            if (jarFile != null) {
                                Enumeration<JarEntry> jarEntries = jarFile.entries();
                                while (jarEntries.hasMoreElements()) {
                                    JarEntry jarEntry = jarEntries.nextElement();
                                    String jarEntryName = jarEntry.getName();
                                    if (StringUtils.endsWith(jarEntryName, ".class")) {
                                        String className = StringUtils.substring(jarEntryName, 0, StringUtils.lastIndexOf(jarEntryName, "."));
                                        addClassToSet(classSet, className);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (IOException e) {
            log.error("无法获取类集合", e);
            throw new RuntimeException(e);
        }
        return classSet;
    }

    /**
     * @param classSet 类的集合
     * @param packagePath 包路径，例如 com/jake/summer/utils
     * @param packageName 包名，例如 com.jake.summer.utils
     */
    private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
        File[] files = new File(packagePath).listFiles(file ->
                (file.isFile() && StringUtils.endsWith(file.getName(), ".class")) || file.isDirectory());
        if (ObjectUtils.isEmpty(files)) {
            throw new RuntimeException("路径" + packagePath + "下不存在类");
        }
        for (File file : files) {
            String fileName = file.getName();
            if (file.isFile()) {
                String className = StringUtils.substring(fileName, 0, StringUtils.lastIndexOf(fileName, "."));
                if (StringUtils.isNotEmpty(packageName)) {
                    // 例如：tutorial.TutorialDemo 或 com.jake.summer.utils.ClassUtil
                    className = packageName + "." + className;
                }
                addClassToSet(classSet, className);
            } else {
                /*
                 * 如果不是文件类型，则将 fileName 拼接到字符串末尾，例如：
                 * aspect 拼接到 com.jake.summer 后面变成 com.jake.summer.aspect
                 */
                String subPackagePath = fileName;
                if (StringUtils.isNotEmpty(packagePath)) {
                    subPackagePath = packagePath + "/" + subPackagePath;
                }
                String subPackageName = fileName;
                if (StringUtils.isNotEmpty(packageName)) {
                    subPackageName = packageName + "." + subPackageName;
                }
                addClass(classSet, subPackagePath, subPackageName);
            }
        }
    }

    /**
     * 将无需进行初始化的 Class 对象存入 Set 中
     * @param classSet 类对象的集合
     * @param referencedClassName 全限定类名，例如：com.jake.summer.annotation.Aspect
     */
    private static void addClassToSet(Set<Class<?>> classSet, String referencedClassName) {
        try {
            classSet.add(ClassUtils.getClass(referencedClassName, false));
        } catch (ClassNotFoundException e) {
            log.error("无法根据全限定名找到类", e);
            throw new RuntimeException(e);
        }
    }

}
